import { Injectable, HostListener } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Client, SearchResponse } from 'elasticsearch';
import { RestService } from './rest.service';

import * as _ from 'lodash';

@Injectable()
export class ElasticsearchService {
  private _client: Client;

  // index in elasticsearch
  ZCINDEX = 'sifo-*';

  // refresh interval in seconds
  INTERVAL = 5;

  // Default order: asc or desc
  SORT_ORDER = '@timestamp:desc';

  // the last record has been retrieved
  lastRecord: any;

  constructor(public restService: RestService) {
    if (!this._client) {
      this._connect();
    }
  }

  private _connect() {
    let host = 'http://' + window.location.hostname + ':' + '9200';
    if (this.restService.isDebugging()) {
      host = 'http://43.226.165.240:9200';
    }

    this._client = new Client({
      host: host,
      log: 'trace'
    });
  }

  /**
   * To get the latest records from elasticsearch.
   */
  getLatestRecord(callback): any {
    this.getLatestRecords(1, results => {
      if (results.data && results.data.length < 1) {
        if (callback) {
          callback(results);
        }

        return;
      }

      results.data = [results.data[0]['_source']];

      if (callback) {
        callback(results);
      }
    });
  }

  getLatestRecords(size, callback): any {
    const results = {
      total: 0,
      data: []
    };

    this._client.search({
      index: this.ZCINDEX,
      size: size,
      // sort: 'TimeStamp:desc',
      sort: this.SORT_ORDER,
      body: {
        _source: {
          excludes: ['source', 'type', 'message', 'beat', 'host', 'input_type', 'tags', '@version']
        },
        query: {
          match_all: {}
        }
      }
    }).then(data => {
      if (data && data.hits && data.hits.hits) {
        results.total = data.hits.total;
        results.data = data.hits.hits;
      }

      if (callback) {
        callback(results);
      }
    }, error => {
      console.log('failed to get data from server.');
      if (callback) {
        callback(results);
      }
    });
  }

  /**
   * Get the records from a specified time frame. size is the request length.
   */
  getRecordsFromTo(fromTimeInMillionseconds, toTimeInMillionseconds, size, callback): any {
    const results = {
      total: 0,
      data: []
    };

    const startTime = Math.round(fromTimeInMillionseconds / 1000);
    const endTime = Math.round(toTimeInMillionseconds / 1000);

    this._client.search({
      index: this.ZCINDEX,
      size: size,
      // sort: 'TimeStamp:desc',
      sort: this.SORT_ORDER,
      body: {
        query: {
          range: {
            'TimeStamp': {
              gte: startTime,
              lte: endTime
            }
          }
        }
      }
    }).then(data => {
      if (data && data.hits && data.hits.hits) {
        results.total = data.hits.total;
        results.data = data.hits.hits;
      }

      if (callback) {
        callback(results);
      }
    }, error => {
      console.log('failed to get data from server.');
      if (callback) {
        callback(results);
      }
    });
  }

  /**
   * Get the last record from a specified time frame.
   */
  getLastRecordsFromTo(fromTimeInMillionseconds, toTimeInMillionseconds): any {
    const results = {
      total: 0,
      data: []
    };

    const startTime = Math.round(fromTimeInMillionseconds / 1000);
    const endTime = Math.round(toTimeInMillionseconds / 1000);

    return this._client.search({
      index: this.ZCINDEX,
      size: 1,
      // sort: 'TimeStamp:desc',
      sort: this.SORT_ORDER,
      body: {
        query: {
          range: {
            'TimeStamp': {
              gte: startTime,
              lte: endTime
            }
          }
        }
      }
    });
  }

  getLastFaultRecord(field, startTime, endTime, callback): any {
    const results = {
      total: 0,
      data: []
    };

    const searchObj = {
      index: this.ZCINDEX,
      size: 1,
      // sort: 'TimeStamp:desc',
      sort: this.SORT_ORDER,
      body: {
        _source: {
          excludes: ['source', 'type', 'message', 'beat', 'host', 'input_type', 'tags', '@version']
        },
        query: {
          bool: {
            must: [{
              range: {
                'TimeStamp': { }
              }
            }, {
              range: {}
            }]
          }
        }
      }
    };

    if (startTime) {
      searchObj.body.query.bool.must[0].range['TimeStamp']['gte'] = Math.round(startTime / 1000);
    }

    if (endTime) {
      searchObj.body.query.bool.must[0].range['TimeStamp']['lte'] = Math.round(endTime / 1000);
    }

    searchObj.body.query.bool.must[1].range[field] = {gte: 1};

    this._client.search(searchObj).then(data => {
      if (data && data.hits && data.hits.hits) {
        results.total = data.hits.total;
        if (_.isEmpty(data.hits.hits)) {
          results.data = [];
        } else {
          results.data = [data.hits.hits[0]['_source']];
        }
      }

      if (callback) {
        callback(results);
      }
    }, error => {
      console.log('failed to get data from server.');
      if (callback) {
        callback(results);
      }
    });
  }

  getLastFaults(startTime, endTime, callback) {
    const table = this.restService.getTable('FaultData');
    if (!table || !table.elements) {
      console.log('Failed to get faults table in schema!');
      callback([]);
      return;
    }

    const faults = [];
    const total = table.elements.length;
    let received = 0;
    let isDone = false;
    table.elements.forEach(element => {
      this.getLastFaultRecord(element.id, startTime, endTime, data => {
        received++;
        if (received === total) {
          isDone = true;
        }

        if (_.isEmpty(data.data)) {
          if (isDone) {
            callback(faults);
          }
          return;
        }

        faults.push({
          train: '001',
          time: this.restService.getUpdatedTime(data.data[0]),
          name: element.name,
          value: data.data[0][element.id]
        });

        if (isDone) {
          callback(faults);
        }
      });
    });
  }

  /**
   * Clear all data, be careful to call this function!!!!
   */
  clearAllData() {
    this._client.indices.delete({
      index: this.ZCINDEX,
      ignore: [404]
    }).then(message => {
      console.log('index was deleted or never existed.');
    }, error => {
      console.error('Failed to remove index.');
    });
  }

  /**
   * Add one index into elastichsearch.
   */
  addToIndex(value): any {
    return this._client.create(value);
  }

  /**
   * Check if elasticseach is alive.
   */
  isAvailable(): any {
    return this._client.ping({
      requestTimeout: Infinity
    }, error => {
      if (error) {
        console.error('elasticsearch cluster is down!');
      } else {
        console.log('elasticsearch is well');
      }
    });
  }
}
